///////////////////////////////////////////////////////////////////////////////////////////////////
/// \file accumulator.h
///
/// \brief   header file for the Accumulator base class
///
///////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef PCPS_ACCUMULATOR_HEADER
#define PCPS_ACCUMULATOR_HEADER

#include<src/pcps.h>
#include<src/input.h>
#include<src/configuration.h>
#include<src/mpi_interface.h>

namespace pcps {

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// \brief  the base class for accumulator objects that collect data during a monte carlo sample
  ///
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  template <class S> class Accumulator {

    protected:

      /// \brief  total weight accumulated at all configurations
      double _total_weight;
      double _naccept;

    public:

      /// \brief  default constructor
      Accumulator() : _total_weight(0.0), _naccept(0.0) {}

      /// \brief  make destructor virtual so that deleting a pointer-to-base will call childrens' destructors
      virtual ~Accumulator() {}

      /// \brief  return the acceptance ratio
      double acceptance_ratio() const { assert( _total_weight != 0.0 ); return _naccept / _total_weight; }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Accumulate data for the given configuration.
      ///
      /// \param[in]      userinp  the user's input options
      /// \param[in]      w        the weight to apply to this configuration's contribution
      /// \param[in]      config   the configuration to accumulate data for
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      void accumulate(const pcps::Input & userinp, const double w, const pcps::Configuration<S> & config) {

        // add the weight of the configuration to the total
        _total_weight += w;

        // count the acceptance
        _naccept += 1.0;

        // perform any special accumulations as specified by the child class
        this->child_accumulate(userinp, w, config);

      }

      virtual void child_accumulate(const pcps::Input & userinp, const double w, const pcps::Configuration<S> & config) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief finalizes the accumulation (this may include for example communicating the results)
      ///
      /// \param[in]      userinp  the user's input options
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      void finalize(const pcps::Input & userinp) {

        // compute the total weight across all processes
        double x = 0.0;
        pcps::mpi::allreduce(&_total_weight, &x, 1, MPI::SUM);
        _total_weight = x;

        // compute the total number of accepts across all processes
        x = 0.0;
        pcps::mpi::allreduce(&_naccept, &x, 1, MPI::SUM);
        _naccept = x;

        // perform any finalizations required by the child
        this->child_finalize(userinp);

      }

      virtual void child_finalize(const pcps::Input & userinp) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief  resets the accumulator
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      void reset(const pcps::Configuration<S> & config) {

        // reset the total weight
        _total_weight = 0.0;
        _naccept = 0.0;

        // perform any resetting required by the child
        this->child_reset(config);

      }

      virtual void child_reset(const pcps::Configuration<S> & config) = 0;

  };

}

#endif
